#pragma once
#include "Helper.hpp"
#include "Variant.hpp"
#include "Tokenizer.hpp"
#include <vector>

namespace Welp
{
  using std::vector;

  class VirtualMachine;

  class SyntaxNode;

  class SyntaxTree
  {
  public:
    SyntaxNode* mRoot;
  };

  class SyntaxNode
  {
  public:
    Location mLocation;
    vector<SyntaxNode*> mChildren;

    virtual ~SyntaxNode();

    virtual Variant Execute(VirtualMachine* vm, Variant* localsTable) = 0;

    template <typename NodeOrBaseType, typename NodeType>
    NodeType* AddChild(NodeOrBaseType*& namedChild, NodeType* node)
    {
      namedChild = node;
      mChildren.push_back(node);
      return node;
    }

    template <typename NodeOrBaseType, typename NodeType>
    NodeType* AddChild(vector<NodeOrBaseType*>& children, NodeType* node)
    {
      children.push_back(node);
      mChildren.push_back(node);
      return node;
    }

    template <typename NodeType>
    NodeType* AddChild(NodeType* node)
    {
      mChildren.push_back(node);
      return node;
    }
  };

  template <typename T>
  void ExecuteAll(vector<T*>& nodes, VirtualMachine* vm, Variant* localsTable)
  {
    for (size_t i = 0; i < nodes.size(); ++i)
    {
      T* node = nodes[i];
      node->Execute(vm, localsTable);
    }
  }

  class StatementNode : public SyntaxNode
  {
  public:
  };

  class ExpressionNode : public StatementNode
  {
  public:
  };

  class FunctionNode : public ExpressionNode
  {
  public:

    // A closure does not create its own locals table, but rather
    // references the locals of the function it is defined within
    bool mIsClosure;

    vector<string> mParameters;
    vector<StatementNode*> mStatements;

    // Executing a function node only returns a variant that contains the function node itself (callable)
    // A function call actually invokes a function node (or user function / callable table)
    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class ReturnException
  {
  public:
    Variant mResult;
  };

  class ReturnNode : public StatementNode
  {
  public:
    
    // The value to be returned (can be empty, which will implictly return null)
    ExpressionNode* mOptionalReturnValue;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class BreakException
  {
  public:
  };

  class BreakNode : public StatementNode
  {
  public:
    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class ContinueException
  {
  public:
  };

  class ContinueNode : public StatementNode
  {
  public:
    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class WhileNode : public StatementNode
  {
  public:
    
    // The condition that we evaluate and test before running the statements
    ExpressionNode* mConditional;
    vector<StatementNode*> mStatements;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class IfPartNode : public StatementNode
  {
  public:
    
    // The condition that we evaluate and test before running the statements
    ExpressionNode* mConditional;
    vector<StatementNode*> mStatements;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class IfNode : public StatementNode
  {
  public:
    
    // All parts of the if must have conditional expressions, except the last one (which is just a straight else)
    vector<IfPartNode*> mParts;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class LiteralNode : public ExpressionNode
  {
  public:
    Variant mValue;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class VariableReferenceNode : public ExpressionNode
  {
  public:
    // Instead of creating a variant from a string every time, we just create a string variant once
    string mName;

    // First looks in the locals table, then the globals table
    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class VariableDeclarationNode : public ExpressionNode
  {
  public:
    string mName;

    // Informs us whether this declaration is intended for locals or globals
    bool mIsLocal;

    // A value to initialize the variable with (otherwise always null)
    ExpressionNode* mOptionalInitialValue;

    // Declares this variable within the local
    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class UnaryOperatorNode : public ExpressionNode
  {
  public:
    TokenType::Enum mOperator;
    ExpressionNode* mOperand;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class BinaryOperatorNode : public ExpressionNode
  {
  public:
    TokenType::Enum mOperator;
    ExpressionNode* mLeft;
    ExpressionNode* mRight;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };

  class MemberAccessNode : public ExpressionNode
  {
  public:
    ExpressionNode* mObject;
    Token mName;
  };

  class IndexerNode : public ExpressionNode
  {
  public:
    ExpressionNode* mIndexable;
    ExpressionNode* mIndex;
  };

  class FunctionCallNode : public ExpressionNode
  {
  public:
    ExpressionNode* mCallable;
    vector<ExpressionNode*> mArguments;

    Variant Execute(VirtualMachine* vm, Variant* localsTable) override;
  };
}
